using System;
using System.Collections.Generic;
using System.Text;

namespace OnlinePlots.Plots.DataSets
{
    public enum InterpolationType
    {
        Step = 0,
        Linear = 1,
        Spline = 2
    }

    public class DiscretePlotDataSet : PlotDataSet
    {
        protected List<double> argumentValues;
        protected List<double> functionValues;
        protected InterpolationType interpolationType;
        protected double lowerArgumentFunctionValue;
        protected double higherArgumentFunctionValue;

        public double LowerArgumentFunctionValue
        {
            get { return lowerArgumentFunctionValue; }
            set { lowerArgumentFunctionValue = value; }
        }

        public double HigherArgumentFunctionValue
        {
            get { return higherArgumentFunctionValue; }
            set { higherArgumentFunctionValue = value; }
        }

        public DiscretePlotDataSet(IEnumerable<double> argumentValues, IEnumerable<double> functionValues, InterpolationType interpolationType, double lowerArgumentFunctionValue, double higherArgumentFunctionValue)
        {
            this.functionValues = new List<double>(functionValues);
            this.argumentValues = new List<double>(argumentValues);
            this.argumentValues.Sort();
            this.interpolationType = interpolationType;
            this.lowerArgumentFunctionValue = lowerArgumentFunctionValue;
            this.higherArgumentFunctionValue = higherArgumentFunctionValue;
        }

        public DiscretePlotDataSet(IEnumerable<double> argumentValues, IEnumerable<double> functionValues, InterpolationType interpolationType)
            : this(argumentValues, functionValues, interpolationType, 0, 0)
        {
            this.lowerArgumentFunctionValue = (this.functionValues.Count > 0) ? this.functionValues[0] : 0;
            this.higherArgumentFunctionValue = (this.functionValues.Count > 0) ? this.functionValues[this.functionValues.Count - 1] : 0;
        }

        public DiscretePlotDataSet(double argumentStartValue, double argumentStep, IEnumerable<double> functionValues, InterpolationType interpolationType)
            : this(new List<double>(), functionValues, interpolationType)
        {
            double argumentEndValue = argumentStartValue + argumentStep * this.functionValues.Count;
            for (double argumentValue = argumentStartValue; argumentValue < argumentEndValue; argumentValue += argumentStep)
            {
                this.argumentValues.Add(argumentValue);
            }
        }

        public override double GetValue(double argument)
        {
            int argumentIndex = argumentValues.BinarySearch(argument);
            if (argumentIndex >= 0)
            {
                return functionValues[argumentIndex];
            }
            else
            {
                argumentIndex = ~argumentIndex;
            }
            if (interpolationType == InterpolationType.Linear)
            {
                if (argumentIndex == 0)
                {
                    return LowerArgumentFunctionValue;
                }
                else if (argumentIndex == argumentValues.Count)
                {
                    return  HigherArgumentFunctionValue;
                }
                else
                {
                    return functionValues[argumentIndex - 1] + (argument - argumentValues[argumentIndex - 1]) * (functionValues[argumentIndex] - functionValues[argumentIndex - 1]) / (argumentValues[argumentIndex] - argumentValues[argumentIndex - 1]);
                }
            }
            else if (interpolationType == InterpolationType.Step)
            {
                if (argumentIndex == 0)
                {
                    return LowerArgumentFunctionValue;
                }
                else if (argumentIndex == argumentValues.Count)
                {
                    return HigherArgumentFunctionValue;
                }
                else
                {
                    return functionValues[argumentIndex];
                }
            }
            else if (interpolationType == InterpolationType.Spline)
            {
                if (argumentIndex == 0)
                {
                    return LowerArgumentFunctionValue;
                }
                else if (argumentIndex == argumentValues.Count)
                {
                    return HigherArgumentFunctionValue;
                }
                else
                {
                    double t = (argument - argumentValues[argumentIndex - 1]) / (argumentValues[argumentIndex] - argumentValues[argumentIndex - 1]);
                    double m1 = 0;
                    if (argumentIndex > 1)
                    {
                        m1 = functionValues[argumentIndex] - functionValues[argumentIndex - 2];
                    }
                    else
                    {
                        double r = functionValues[argumentIndex - 1]  - (functionValues[argumentIndex] - functionValues[argumentIndex - 1]) * (argumentValues[argumentIndex] - argumentValues[argumentIndex - 1]);
                        m1 = functionValues[argumentIndex] - r;
                    }
                    double m2 = 0;
                    if (argumentIndex < argumentValues.Count - 1)
                    {
                        m2 = functionValues[argumentIndex + 1] - functionValues[argumentIndex - 1];
                    }
                    else
                    {
                        m2 = functionValues[argumentIndex] - functionValues[argumentIndex - 1];
                    }
                    double p1 = functionValues[argumentIndex - 1];
                    double p2 = functionValues[argumentIndex];
                    return (2 * t * t * t - 3 * t * t + 1) * p1 + (t * t * t - 2 * t * t + t) * m1 / 2 + (-2 * t * t * t + 3 * t * t) * p2 + (t * t * t - t * t) * m2 / 2;
                }
            }
            return 0;
        }
    }
}
